/**
 *
 * 文本标记 api函数库
 * 你可以将此库的函数自有组装，来自有实现自己的功能，此库依赖于jquery
 *高亮代码 <zh class="z-highlight" data-id="zh-1435820385354">This is highlighted content</span>
 *
 * @Function registerRootSelectEvent(rootElement, callback)  |【注册选择区域dom的mouse | touch事件】
 *
 *
 *
 *
 *
 *
 * */
window.ZHighlight = (function (){
    let jq = null;
    let zhIdPrefix = 'zh-';
    let isSelectRange = null;//是否处于选择状态
    let mouse = {pageX: 0, pageY:0, offsetX:0, offsetY: 0};
    let annotations = [];
    let clickTimer = null


    /** 获取选区的 range
     * @return Range | null
     * */
    function getRange(allowSelectMiniLength){
        let selection = window.getSelection(), selectedText = null;
        if(selection.text) {
            selectedText = selection.text;
        } else {
            selectedText = selection.toString();
        }
        if(selection && !selection.isCollapsed && selectedText && selectedText.length > allowSelectMiniLength) {
            return selection.getRangeAt(0)
        }
    }

    /**
     * 获取父级节点
     * @return Node
     * */
    function getParentNodeFor(node){
        while(node.nodeType != 1) {
            node = node.parentNode
        }
        return node
    }
    /**
     * 获取需要包裹的节点，这样可以计算开始结束index
     * @return Nodes Array
     * */
    function getNodesToWrap(rootNode, startNode, endNode) {
        let pastStartNode = false, reachedEndNode = false, textNodes = [];
        function getTextNodes(node) {

            if (node == startNode) {
                pastStartNode = true;
            }
            if (node == endNode) {
                reachedEndNode = true;
            } else if (node.nodeType == Node.TEXT_NODE) {
                if (pastStartNode && !reachedEndNode && !/^\s*$/.test(node.nodeValue)) {
                    textNodes.push(node);
                }
            } else if (node.nodeType == Node.ELEMENT_NODE ) {

                for (var i = 0, len = node.childNodes.length; !reachedEndNode && i < len; ++i) {
                    getTextNodes(node.childNodes[i]);
                }
            }
        }
        getTextNodes(rootNode);
        return textNodes;
    }

    /**
     * 获取一个随机id
     * @return String
     * */
    function getRandomId(){
        return zhIdPrefix + Date.now()
    }

    /**
     * 判断选区是否包含指定的样式，这样可以过滤固定的 NodeName、className、id 或 style
     * @return Boolean
     * */
    function selectionContains({ nodeNames = [], classNames = [], ids = [], styles = [] }) {
        const selection = window.getSelection();
        if (!selection.rangeCount) return false;

        for (let i = 0; i < selection.rangeCount; i++) {
            const range = selection.getRangeAt(i);
            let nodes = range.cloneContents().querySelectorAll("*");
            if(!nodes.length){
                if(range.commonAncestorContainer){
                    nodes = [range.commonAncestorContainer.parentNode]
                }else{
                    nodes = [range.startContainer.parentNode]
                }
            }

            for (const node of nodes) {
                if (
                    (nodeNames.length && nodeNames.some(name => node.nodeName.toLowerCase() === name.toLowerCase())) ||
                    (classNames.length && classNames.some(cls => node.classList.contains(cls))) ||
                    (ids.length && ids.includes(node.id)) ||
                    (styles.length && styles.some(style => node.getAttribute("style")?.includes(style)))
                ) {
                    return true;
                }
            }
        }
        return false;
    }

    /** 获取选区文本内容*/
    function getSelectionContent(range){
        let container = document.createElement("div");
        container.appendChild(range.cloneContents());
        let text = container.textContent;
        return text;
    }

    /** 将节点信息转成 XPATH 数据格式*/
    function createXPathFromElement(rootDom, elm){
        var allNodes = document.getElementsByTagName('*');
        for (var segs = []; elm && elm.nodeType == 1 && elm != rootDom.parentNode; elm = elm.parentNode) {
            if (elm.hasAttribute('id')) {
                var uniqueIdCount = 0;
                for (var n=0;n < allNodes.length;n++) {
                    if (allNodes[n].hasAttribute('id') && allNodes[n].id == elm.id) uniqueIdCount++;
                    if (uniqueIdCount > 1) break;
                };

                if ( uniqueIdCount == 1) {
                    segs.unshift('id("' + elm.getAttribute('id') + '")');
                    return segs.join('/');
                } else {
                    segs.unshift(elm.localName.toLowerCase() + '[@id="' + elm.getAttribute('id') + '"]');
                }

            } else if (elm.hasAttribute('class')) {
                segs.unshift(elm.localName.toLowerCase() + '[@class="' + elm.getAttribute('class') + '"]');
            } else {
                for (i = 1, sib = elm.previousSibling; sib; sib = sib.previousSibling) {
                    if (sib.localName == elm.localName)  i++;
                };
                segs.unshift(elm.localName.toLowerCase() + '[' + i + ']');
            }
        }

        return segs.length ? '' + segs.join('/') : null;
    }

    function getNodeAndOffset(range){
        let parentContainer = range.commonAncestorContainer;
        let startContainer = range.startContainer;
        let endContainer = range.endContainer;

        let startOffset = range.startOffset;
        let endOffset = range.endOffset;

        let parentNode = getParentNodeFor(parentContainer);
        let startNode = getParentNodeFor(startContainer);
        let endNode = getParentNodeFor(endContainer);

        let nodesBetweenStart = getNodesToWrap(parentNode, startNode.firstChild, startContainer);
        let nodesBetweenEnd = getNodesToWrap(parentNode, endNode.firstChild, endContainer);

        if(nodesBetweenStart.length) {
            for(let i = 0; i < nodesBetweenStart.length; i++) {
                let characterLength = nodesBetweenStart[i].nodeValue.length;
                startOffset += characterLength;
            }
        }

        if(nodesBetweenEnd.length) {
            for(var i = 0; i < nodesBetweenEnd.length; i++) {
                endOffset += nodesBetweenEnd[i].nodeValue.length;
            }
        }

        let selectedContent = getSelectionContent(range);
        return {
            startNode: startNode,
            endNode: endNode,
            parentNode: parentNode,
            startOffset: startOffset,
            endOffset: endOffset,
            selectedText: selectedContent
        }
    }
    /** 获取元素的偏移量*/
    function getTextNodeAtOffset(rootNode, offset) {
        if(!rootNode){
            return
        }
        var textNode,
            count = 0,
            found = false;

        function getTextNodes(node) {
            if (node.nodeType == Node.TEXT_NODE && !/^\s*$/.test(node.nodeValue)) {
                if ( found != true) {
                    if(count+node.nodeValue.length >= offset) {
                        textNode = node;
                        found = true;
                    } else {
                        count += node.nodeValue.length
                    }
                }
            } else if (node.nodeType == Node.ELEMENT_NODE ) {
                for (var i = 0, len = node.childNodes.length; i < len; ++i) {
                    getTextNodes(node.childNodes[i]);
                }
            }
        }

        getTextNodes(rootNode);
        return [textNode, (count == 0 ? offset : offset - count)];
    }

    /** 获取选区包含的所有节点*/
    function getContainedNodes(_startOffset, _endOffset, $parentContainer, $startContainer, $endContainer){
        var startContainer, endContainer, parentContainer;
        var nodes = [];

        parentContainer = $parentContainer.get(0);
        startContainer = $startContainer.get(0);
        endContainer = $endContainer.get(0);

        if(!startContainer || !endContainer){
            return
        }

        var startTextNodeParams = getTextNodeAtOffset(startContainer, _startOffset),
            endTextNodeParams = getTextNodeAtOffset(endContainer, _endOffset);

        var startTextNode = startTextNodeParams[0],
            startOffset = startTextNodeParams[1],
            endTextNode = endTextNodeParams[0],
            endOffset = endTextNodeParams[1];


        if(startTextNode == endTextNode) {
            var startTextNodeSplit = startTextNode.splitText(startOffset);
            var endTextNodeSplit = startTextNodeSplit.splitText(endOffset - startOffset);
        } else {
            var startTextNodeSplit = startTextNode.splitText(startOffset);
            var endTextNodeSplit = endTextNode.splitText(endOffset);
        }


        var innerNodes = getNodesToWrap(parentContainer, startTextNodeSplit, endTextNodeSplit);


        for(var i = 0; i < innerNodes.length; i++) {
            nodes.push(innerNodes[i]);
        }

        return nodes;
    }

    /** 获取指定XPATH的元素*/
    function findElementByXPath(rootDom, path){
        let evaluator = new XPathEvaluator();
        let result = evaluator.evaluate(path, rootDom, null,XPathResult.FIRST_ORDERED_NODE_TYPE, null);
        return  result.singleNodeValue;
    }

    /** 移除选区*/
    function removeTemporary(rootDom, selectClassName) {
        var temporary = jq(rootDom).find(selectClassName);
        for(var i = 0; i < temporary.length; i++) {
            var elem = temporary[i];
            jq(elem.childNodes[0]).unwrap();
        }
    }

    /**包裹内容*/
    function wrapNode(wrapNode, nodes){
        if(!nodes){
            return
        }
        for(let i = 0; i < nodes.length; i++) {
            jq(nodes[i]).wrap(wrapNode)
        }
    }

    return {
        /** 注册一个局部JQuery*/
        registerJQuery($){
            jq = $
        },
        /** 注册选择区域dom的mouse | touch事件*/
        registerRootSelectEvent(dom, fn){
            jq(dom).on("mousedown touchstart", function(e) {
                console.log(e)
                mouse.pageX = e.pageX
                mouse.pageY = e.pageY
                mouse.offsetY = e.offsetY
                mouse.offsetX = e.offsetX
            })
            jq(dom).on("mouseup touchend", function(e) {
                e.preventDefault();
                e.stopPropagation();
                //缓存当前是否正则框选
                isSelectRange = true
                e.mouse = mouse
                fn && fn(e)
                setTimeout(() => {
                    //清除当前框选状态
                    isSelectRange = false
                }, 100)
            })
        },
        /**
         * 注册一个选区的click、hover等等事件，用于点击高亮元素
         * */
        registerHighlightNodeClickEvent(dom, className, fn){
            jq(dom).on('click', className, function(e) {
                e.stopPropagation();
                clickTimer && clearTimeout(clickTimer);
                clickTimer = setTimeout(() => {
                    if(!isSelectRange){
                        fn && fn(e)
                    }
                }, 50)
            });
        },
        registerRootEvent(dom, type, fn){
            jq(dom).on(type, fn);
        },
        getRange(allowSelectMiniLength){
           return getRange(allowSelectMiniLength)
        },
        /** 将选区按照指定标签包裹 */
        wrapSelectContent(rootDom, range, createWrapNode){
            const nodeInfo = getNodeAndOffset(range)
            const parentContainerXPath = createXPathFromElement(rootDom, nodeInfo.parentNode)
            const startContainerXPath = createXPathFromElement(rootDom, nodeInfo.startNode)
            const endContainerXPath = createXPathFromElement(rootDom, nodeInfo.endNode)

            const $parentContainer = jq(findElementByXPath(rootDom, parentContainerXPath));
            const $startContainer = jq(findElementByXPath(rootDom, startContainerXPath));
            const $endContainer = jq(findElementByXPath(rootDom, endContainerXPath));
            const nodes = getContainedNodes(nodeInfo.startOffset, nodeInfo.endOffset, $parentContainer, $startContainer, $endContainer)
            wrapNode(createWrapNode, nodes)
            return {
                range: {
                    startOffset: nodeInfo.startOffset,
                    endOffset: nodeInfo.endOffset,
                    startContainerXPath: startContainerXPath,
                    endContainerXPath: endContainerXPath,
                    parentContainerXPath: parentContainerXPath
                },
                selectedText: nodeInfo.selectedText
            }
        },
        /** 判断选区是否包含指定的样式，这样可以过滤固定的 NodeName、className、id 或 style */
        selectionContains(options){
            return selectionContains(options)
        },
        /** 清空选区 */
        clearSelectRange(){
            if (window.getSelection) {
                window.getSelection().removeAllRanges();
            } else if (document.selection) {
                document.selection.empty();
            }
        },
        getRandomId(){
            return getRandomId()
        },
        removeTemporary(rootDom, selectClassName){
            return removeTemporary(rootDom, selectClassName)
        },
        getPosition(e, width, height, offset){
            if(!offset){
                offset = {top: 0, left: 0}
            }
            const w = window.innerWidth, h = window.innerHeight;
            const y = e.pageY, x = e.pageX;
            let top = e.mouse.pageY - offset.y - 20;
            let left = e.mouse.pageX - offset.x;
            if(e.mouse.pageX > x){
                left = x - offset.x
            }
            if(e.mouse.pageY > y){
                top = y - height - 20
            }
            if(left + width > w){
                left = w - width
            }
            if(top + height > h){
                top = h - height
            }
            if(top - height < 0){
                if(e.mouse.pageY < y){
                    top = y + 20
                }else{
                    top = e.mouse.pageY + 20
                }
            }
            return {
                x: left,
                y: top
            }
        },
        /** 将知道的元素转成高亮元素*/
        setNodeAttribute(node, attrs){
            for(let key in attrs){
                key = key.toString()
                if(key.indexOf('data-') > -1){
                    node.setAttribute(key, attrs[key])
                }
                if(key === 'classNames' && Array.isArray(attrs[key])){
                    attrs[key].map(function (className){
                        node.classList.add(className)
                    })
                }
            }
        }
    }
})()